﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;

namespace devtm.Aop.Extensions
{


    public static class TypeReferenceExt
    {


        public static bool NeedBox(this TypeReference type, TypeDefinition target)
        {

            if (type.IsValueType)
            {

            }
            return false;
        }

 
        public static TypeReference CloseGenericParameter(this TypeReference type, TypeReference template, TypeDefinition target)
        {
            //return type.IsGenericParameter ? genericArguments.ElementAt(((GenericParameter)type).Position) : type;
            var genericTemplate = template as GenericInstanceType;
            if (type.IsGenericParameter && genericTemplate != null)
            {
                return genericTemplate.GenericArguments.ElementAt(((GenericParameter)type).Position);
            }
            if (type.IsGenericInstance && genericTemplate != null)
            {
                return ((GenericInstanceType)type).GetClosedGenericInstanceType(genericTemplate, target);
            }
            return target.Module.Import(type);
        }


        public static TypeReference CloseType(
                        this TypeReference reference,
                        IEnumerable<TypeReference> genericArguments)
        {
            if (!reference.IsGenericInstance) return reference;
            var genericInstanceType = new GenericInstanceType(reference.GetElementType());
            genericInstanceType.GenericArguments.AddRange(genericArguments);
            return genericInstanceType;
        }


        public static ArrayType MakeArrayType(this TypeReference self)
        {
            return new ArrayType(self);
        }


        public static ArrayType MakeArrayType(this TypeReference self, int rank)
        {
            if (rank == 0)
                throw new ArgumentOutOfRangeException("rank");

            var array = new ArrayType(self);

            for (int i = 1; i < rank; i++)
                array.Dimensions.Add(new ArrayDimension());

            return array;
        }


        public static PointerType MakePointerType(this TypeReference self)
        {
            return new PointerType(self);
        }


        public static ByReferenceType MakeByReferenceType(this TypeReference self)
        {
            return new ByReferenceType(self);
        }


        public static OptionalModifierType MakeOptionalModifierType(this TypeReference self, TypeReference modifierType)
        {
            return new OptionalModifierType(modifierType, self);
        }


        public static RequiredModifierType MakeRequiredModifierType(this TypeReference self, TypeReference modifierType)
        {
            return new RequiredModifierType(modifierType, self);
        }


        public static GenericInstanceType MakeGenericInstanceType(this TypeReference self, params TypeReference[] arguments)
        {
            if (self == null)
                throw new ArgumentNullException("self");

            if (arguments == null)
                throw new ArgumentNullException("arguments");

            if (arguments.Length == 0)
                throw new ArgumentException();

            if (self.GenericParameters.Count != arguments.Length)
                throw new ArgumentException();

            var instance = new GenericInstanceType(self);

            foreach (var argument in arguments)
                instance.GenericArguments.Add(argument);

            return instance;
        }


        public static PinnedType MakePinnedType(this TypeReference self)
        {
            return new PinnedType(self);
        }


        public static SentinelType MakeSentinelType(this TypeReference self)
        {
            return new SentinelType(self);
        }


        public static Mono.Collections.Generic.Collection<MethodReference> GetMethods(this TypeReference self)
        {

            Mono.Collections.Generic.Collection<TypeReference> refs = null;
            GenericInstanceType g = self as GenericInstanceType;
            if (g != null && g.HasGenericArguments)
                refs = g.GenericArguments;

            Mono.Collections.Generic.Collection<MethodReference> list 
                = new Mono.Collections.Generic.Collection<MethodReference>();

            foreach (var item in self.GetDefinition().Methods)
            {

                var p = item.CloseMethod(refs);

                list.Add(p);

            }

            return list;

        }



        public static MethodDefinition AsMethod(this TypeReference type, MethodReference methodSign)
        {

            List<TypeReference> r = new List<TypeReference>();

            foreach (var item in methodSign.Parameters)            
                r.Add(item.ParameterType);            

            return type.AsMethod(methodSign.Name, r.ToArray());

        }


        public static PropertyDefinition Property(this TypeReference type, string name)
        {

            TypeDefinition t = (type as TypeDefinition) ?? type.Resolve();

            foreach (var item in t.Properties)
                if (item.Name == name)
                    return item;

            if (t.BaseType != null)
                return Property(t.BaseType, name);

            return null;

        }


        public static MethodDefinition AsMethod(this TypeReference type, string name, params TypeReference[] args)
        {

            TypeReference[] types = new TypeReference[args.Length];


            GenericInstanceType genericType = type as GenericInstanceType;
            TypeDefinition mabase = type.Resolve();

            for (int i = 0; i < types.Length; i++)
            {

                TypeReference t = args[i];

                if (!type.IsGenericInstance && !t.IsGenericParameter)
                    types[i] = t;

                else
                {

                    bool test = false;

                    for (int i2 = 0; i2 < mabase.GenericParameters.Count; i2++)
                    {
                        var hh = mabase.GenericParameters[i2];
                        var jj =genericType.GenericArguments[i2];
                        if (jj.FullName == t.FullName)
                        {
                            types[i] = hh;
                            test = true;
                            break;
                        }
                    }

                    if (!test)
                        types[i] = t;


                }

            }


            List<MethodDefinition> l = new List<MethodDefinition>();

            foreach (MethodDefinition m in type.FindMethodsByName(name))
            {

                if (types != null && types.Length > 0)
                {
                    if (m.Parameters.Count == types.Length)
                    {

                        bool test = false;

                        for (int i = 0; i < types.Length; i++)
                            if (m.Parameters[i].ParameterType.FullName != types[i].FullName)
                            {
                                test = true;
                                continue;

                            }

                        if (!test)
                            l.Add(m);
                    }
                }
                else
                {
                    l.Add(m);
                }
            }
            


            int c = l.Count();

            if (c == 1)
                return l[0];
            if (c > 1)
                throw new Exception("the call is ambiguous between methods");
            else
            {
                MethodDefinition me = type.Resolve().BaseType.AsMethod(name, types);
                if (me != null)
                    return me;
            }

            throw new Exception("missing method");
        }


        public static IEnumerable<MethodDefinition> FindMethodsByName(this TypeReference type, string name)
        {

            TypeDefinition t = type.Resolve();

            foreach (MethodDefinition item in t.Methods)
                if (item.Name == name)
                    yield return item;

        }


        public static TypeDefinition GetDefinition(this TypeReference type)
        {

            if (type.IsDefinition)
                return type as TypeDefinition;

            if (type.IsGenericInstance)
            {
                GenericInstanceType g = type as GenericInstanceType;
                var r = g.Resolve();
                return r;
            }

            TypeDefinition def = type.Resolve();

            return def;

        }
        

    }
}
